package bin_tree.heap;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;

/**
 * 基于整型的最大堆实现
 * 此时根节点从0开始编号,若此时节点编号为k
 * 左子树的编号 = 2k + 1;
 * 右子树的编号 = 2k + 2;
 * 父节点的编号 = (k - 1) / 2
 * @author yuisama
 * @date 2022/02/28 19:51
 **/
public class MaxHeap {
    // 使用JDK的动态数组(ArrayList)来存储一个最大堆
    List<Integer> data;
    public MaxHeap() {
        // 构造方法的this调用
        this(10);
    }
    // 初始化的堆大小
    public MaxHeap(int size) {
        data = new ArrayList<>(size);
    }

    /**
     * 将任意数组堆化
     * @param arr
     */
    public MaxHeap(int[] arr) {
        data = new ArrayList<>(arr.length);
        // 1.先将arr的所有元素复制到data数组中
        for (int i : arr) {
            data.add(i);
        }
        // 2.从最后一个非叶子结点开始进行siftDown
        for (int i = parent(data.size() - 1); i >= 0; i--) {
            siftDown(i);
        }
    }

    /**
     * 向最大堆中添加一个新元素val
     * @param val
     */
    public void add(int val) {
        // 1.直接向数组末尾添加元素
        data.add(val);
        // 2.进行元素的上浮操作
        siftUp(data.size() - 1);
    }

    /**
     * 取出当前最大堆的最大值
     * @return
     */
    public int extractMax() {
        // 取值一定注意判空
        if (isEmpty()) {
            throw new NoSuchElementException("heap is empty!cannot extract!");
        }
        int max = data.get(0);
        // 1.将数组末尾元素顶到堆顶
        int lastVal = data.get(data.size() - 1);
        data.set(0,lastVal);
        // 2.将数组末尾的元素删除
        data.remove(data.size() - 1);
        // 3.进行元素的下沉操作
        siftDown(0);
        return max;
    }

    public int peekMax() {
        if (isEmpty()) {
            throw new NoSuchElementException("heap is empty!cannot peek");
        }
        return data.get(0);
    }

    /**
     * 元素的下沉操作
     * @param k
     */
    private void siftDown(int k) {
        // 还存在子树
        while (leftChild(k) < data.size()) {
            int j = leftChild(k);
            // 判断一下是否有右子树
            if (j + 1 < data.size() && data.get(j + 1) > data.get(j)) {
                // 此时右树存在且大于左树的值
                j = j + 1;
            }
            // 此时j就对应左右子树的最大值
            // 和当前节点k去比较
            if (data.get(k) >= data.get(j)) {
                // 下沉结束
                break;
            }else {
                swap(k,j);
                k = j;
            }
        }
    }

    /**
     * 元素上浮操作
     * @param k
     */
    private void siftUp(int k) {
        // 上浮操作的终止条件: 已经走到根节点 || 当前节点值 <= 父节点值
        // 循环的迭代条件 : 还存在父节点并且当前节点值 > 父节点值
        while (k > 0 && data.get(k) > data.get(parent(k))) {
            // 交换当前节点和父节点值
            swap(k,parent(k));
            k = parent(k);
        }
    }

    private void swap(int i, int j) {
        int temp = data.get(i);
        data.set(i,data.get(j));
        data.set(j,temp);
    }

    public boolean isEmpty() {
        return data.size() == 0;
    }

    // 根据索引得到父节点的索引
    private int parent(int k) {
        return (k - 1) >> 1;
    }

    // 根据索引得到左子树索引
    private int leftChild(int k) {
        return (k << 1) + 1;
    }

    // 根据索引得到右子树索引
    private int rightChild(int k) {
        return (k << 1) + 2;
    }

    @Override
    public String toString() {
        return data.toString();
    }
}